#include <cstdio>
#include <map>
#include <string>
#include <unistd.h>

#include "hilog/log.h"
#include "ai_service_log.h"
#include "ai_service_errors.h"
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "ai_service_napi_error.h"
#include "ai_service_napi_utils.h"
#include "ai_service_client.h"

namespace OHOS {
namespace AIS {
namespace {
    constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, AIS_LOG_DOMAIN, "AIServiceNapi"};
    constexpr size_t PARAM0 = 0;
    constexpr size_t PARAM1 = 1;
    constexpr size_t PARAM2 = 2;
    constexpr size_t ARGS_ONE = 1;
    constexpr size_t ARGS_THREE = 3;
    constexpr int SIZE = 1048576;

int32_t ParsePicDesc(napi_env env, napi_value args, PicDescNapi &picDescNapi) {
    napi_valuetype valueType;
    napi_status status = napi_typeof(env, args, &valueType);
    if ((status != napi_ok) || (valueType != napi_object)) {
        AIS_HILOGE("PicDesc is invalid.");
        return RETCODE_JS_NOT_EXISTED_PARAM;
    }

    bool has_width_property = false;
    bool has_height_property = false;
    bool has_dataSize_property = false;

    int32_t width = 0;
    int32_t height = 0;
    int32_t dataSize = 0;

    status = napi_has_named_property(env, args, "width", &has_width_property);
    if (status != napi_ok) {
        AIS_HILOGE("can not find width property.");
        return RETCODE_JS_INVALID_OPERATION;
    }
    if (!has_width_property) {
        return RETCODE_JS_NOT_EXISTED_PARAM;
    }
    if (!GetPropertyInt32(env, args, "width", width)) {
    return RETCODE_JS_INVALID_OPERATION;
    }

    status = napi_has_named_property(env, args, "height", &has_height_property);
    if (status != napi_ok) {
        AIS_HILOGE("can not find height property.");
        return RETCODE_JS_INVALID_OPERATION;
    }
    if (!has_height_property) {
        return RETCODE_JS_NOT_EXISTED_PARAM;
    }
    if (!GetPropertyInt32(env, args, "height", height)) {
    return RETCODE_JS_INVALID_OPERATION;
    }

    status = napi_has_named_property(env, args, "dataSize", &has_dataSize_property);
    if (status != napi_ok) {
        AIS_HILOGE("can not find dataSize property.");
        return RETCODE_JS_INVALID_OPERATION;
    }
    if (!has_dataSize_property) {
        return RETCODE_JS_NOT_EXISTED_PARAM;
    }
    if (!GetPropertyInt32(env, args, "dataSize", dataSize)) {
        return RETCODE_JS_INVALID_OPERATION;
    }

    picDescNapi.width = width;
    picDescNapi.height = height;
    picDescNapi.dataSize = dataSize;

    return RETCODE_SUCCESS;
}

napi_status SetInferResult(napi_env env, napi_value result, InferResult& inferResult) {
    napi_value objects = nullptr;
    napi_create_array_with_length(env, inferResult.count, &objects);
    for (int i = 0; i < inferResult.count; i++) {
        napi_value object = nullptr;
        napi_status status = napi_create_object(env, &object);
        if (status != napi_ok) {
            AIS_HILOGE("napi_create_object failed.");
            return status;
        }
        napi_value left  = nullptr;
        status = napi_create_double(env, inferResult.objects[i].left, &left);
        status = napi_set_named_property(env, object, "left", left);

        napi_value top  = nullptr;
        status = napi_create_double(env, inferResult.objects[i].top, &top);
        status = napi_set_named_property(env, object, "top", top);

        napi_value right  = nullptr;
        status = napi_create_double(env, inferResult.objects[i].right, &right);
        status = napi_set_named_property(env, object, "right", right);

        napi_value bottom  = nullptr;
        status = napi_create_double(env, inferResult.objects[i].bottom, &bottom);
        status = napi_set_named_property(env, object, "bottom", bottom);

        napi_value prop  = nullptr;
        status = napi_create_double(env, inferResult.objects[i].prop, &prop);
        status = napi_set_named_property(env, object, "prop", prop);

        napi_value name  = nullptr;
        status = napi_create_string_utf8(env, inferResult.objects[i].name.c_str(), NAPI_AUTO_LENGTH, &name);
        status = napi_set_named_property(env, object, "name", name);

        if (status != napi_ok) {
        AIS_HILOGE("napi_create_xxx or napi_set_named_property failed. code: %{public}d", status);
        return status;
    }
 
        status = napi_set_element(env, objects, i, object);
        if (status != napi_ok) {
            AIS_HILOGE("napi_set_element failed. code: %{public}d", status);
            return status;
        }
    }

    napi_status status = napi_set_named_property(env, result, "objects", objects);
    if (status != napi_ok) {
        AIS_HILOGE("napi_set_named_property failed. code: %{public}d", status);
        return status;
    }

    napi_value count  = nullptr;
    status = napi_create_int32(env, inferResult.count, &count);
    status = napi_set_named_property(env, result, "count", count);
    if (status != napi_ok) {
        AIS_HILOGE("napi_create_int32 or napi_set_named_property failed. code: %{public}d", status);
        return status;
    }

    return napi_ok;
}

static void ExecuteCB(napi_env env, void *data) {
    AIServiceAsyncContext *context = (AIServiceAsyncContext *)data;
    AIS_HILOGD("ExecuteCB start.");
    AIS_HILOGD("modelId: %{public}d, width: %{public}d, height: %{public}d, dataSize: %{public}d",
        context->modelId, (int)context->picDesc.width, (int)context->picDesc.height, (int)context->picDesc.dataSize);
    auto &client = AIServiceClient::GetInstance();
    InferResult inferResult {};
    auto ret = client.Process(context->modelId, context->picDesc, context->data, inferResult);
    if (ret != RETCODE_SUCCESS) {
        AIS_HILOGE("napi process failed.");
        context->status = FAIL;
    }
    context->status = SUCCESS;
    context->inferResult = inferResult;
}

static void PromiseCompleteCB(napi_env env, napi_status status, void *data) {
    AIS_HILOGD("PromiseCompleteCB start.");
    AIServiceAsyncContext *context = (AIServiceAsyncContext *)data;
    napi_value result = nullptr;
    napi_value undefinedResult = nullptr;
    napi_get_undefined(env, &undefinedResult);

    status = napi_create_object(env, &result);
    if (status != napi_ok) {
        AIS_HILOGE("napi_create_object failed.");
        napi_reject_deferred(env, context->deferred, undefinedResult);
    }
    status = SetInferResult(env, result, context->inferResult);
    if (status != napi_ok) {
        AIS_HILOGE("SetInferResult failed.");
        napi_reject_deferred(env, context->deferred, undefinedResult);
    }

    if (!context->status)
    {
        AIS_HILOGE("failed.");
        napi_reject_deferred(env, context->deferred, undefinedResult);
    } else {
        AIS_HILOGD("PromiseCompleteCB success.");
        napi_resolve_deferred(env, context->deferred, result);
    }
    
    if (context->callbackRef != nullptr) {
    napi_delete_reference(env, context->callbackRef);
    }

    napi_delete_async_work(env, context->asyncWork);
}

static void SetData(std::unique_ptr<AIServiceAsyncContext>& asyncContext, int32_t modelId,
 PicDescNapi& picDescNapi, std::string& data)
{
    asyncContext->picDesc.width = picDescNapi.width;
    asyncContext->picDesc.height = picDescNapi.height;
    asyncContext->picDesc.dataSize = picDescNapi.dataSize;
    asyncContext->modelId = modelId;
    asyncContext->data = data;
}
}

static napi_value Init(napi_env env, napi_callback_info info)
{
    AIS_HILOGD("napi start Init.");
    napi_value result = nullptr;
    size_t argc = ARGS_ONE;
    napi_value argv[ARGS_ONE] = {nullptr};
    napi_value thisArg;
    napi_value undefinedResult = nullptr;
    napi_get_undefined(env, &undefinedResult);
    int32_t ret = napi_get_cb_info(env, info, &argc, argv, &thisArg, nullptr);

    auto &client = AIServiceClient::GetInstance();
    std::string modelPath = "";
    char char_buf[SIZE];
    size_t buf_length = 0;
    auto status = napi_get_value_string_utf8(env, argv[PARAM0], char_buf, SIZE, &buf_length);
    if ((status != napi_ok) || (buf_length <= 0)) {
        return undefinedResult;
    }
    modelPath.assign(char_buf, char_buf + buf_length);
    AIS_HILOGI("modelPath: %{public}s", modelPath.c_str());

    int32_t modelId;
    ret = client.Init(modelPath, &modelId);
    if (ret != RETCODE_SUCCESS) {
        AIS_HILOGE("napi Init failed.");
    }
    status = napi_create_int32(env, modelId, &result);
    if (status != napi_ok) {
        AIS_HILOGE("napi_create_int32 error.");
        return undefinedResult;
    }

    return result;
}

static napi_value DeInit(napi_env env, napi_callback_info info)
{
    AIS_HILOGD("napi start DeInit.");
    napi_value result = nullptr;
    auto ret = AIServiceClient::GetInstance().DeInit();
    napi_create_int32(env, ret, &result);
    return result;
}

static napi_value Process(napi_env env, napi_callback_info info)
{
    AIS_HILOGD("napi start Process.");
    
    napi_value promise = nullptr;
    size_t argc = ARGS_THREE;
    napi_value argv[ARGS_THREE] = {nullptr};
    napi_value thisArg;
    int32_t ret = napi_get_cb_info(env, info, &argc, argv, &thisArg, nullptr);
    int32_t modelId = 0;
    PicDescNapi picDescNapi {};
    std::string data = "";
    napi_value undefinedResult = nullptr;
    napi_get_undefined(env, &undefinedResult);
    std::unique_ptr<AIServiceAsyncContext> asyncContext = std::make_unique<AIServiceAsyncContext>();
    if (asyncContext == nullptr) {
        AIS_HILOGE("AIServiceAsyncContext object create failed.");
        return undefinedResult;
    }

    for (size_t i = PARAM0; i < argc; i++) {
        if (i == PARAM0) {
            napi_status status = napi_get_value_int32(env, argv[i], &modelId);
            if ((status != napi_ok) || (modelId < 0)) {
                return undefinedResult;
            }
        } else if (i == PARAM1) {
            ret = ParsePicDesc(env, argv[i], picDescNapi);
            if (ret != RETCODE_SUCCESS) {
                return undefinedResult;
            }
        } else if (i == PARAM2) {
            char *array_buffer_data;
            size_t array_buffer_total;
            napi_status status = napi_get_arraybuffer_info(env, argv[i],
                reinterpret_cast<void **>(&array_buffer_data), &array_buffer_total);
            if ((status != napi_ok) || (array_buffer_total <= 0)) {
                return undefinedResult;
            }
            data.assign(array_buffer_data, array_buffer_data + array_buffer_total);
        } else {
            AIS_HILOGE("Invalid input params.");
            return undefinedResult;
        }
    }

    AIS_HILOGD("modelId: %{public}d, width: %{public}d, height: %{public}d, dataSize: %{public}d",
        modelId, (int)picDescNapi.width, (int)picDescNapi.height, (int)picDescNapi.dataSize);
    AIS_HILOGD("data.size(): %{public}d,", (int)data.size());

    SetData(asyncContext, modelId, picDescNapi, data);

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "Process", NAPI_AUTO_LENGTH, &resourceName);
    auto status = napi_create_promise(env, &asyncContext->deferred, &promise);
    if (status != napi_ok) {
      AIS_HILOGE("create callback failed.");
      return undefinedResult;
    }
    status = napi_create_async_work(
        env, nullptr, resourceName, 
        ExecuteCB, PromiseCompleteCB, 
        static_cast<void *>(asyncContext.get()), &asyncContext->asyncWork);
    if (status != napi_ok) {
        AIS_HILOGE("napi_create_async_work failed.");
        return undefinedResult;
    }

    status = napi_queue_async_work(env, asyncContext->asyncWork);
    if (status == napi_ok) {
        asyncContext.release();
    } else {
        return undefinedResult;
    }
    AIS_HILOGI("process async end.");
    return promise;
}


static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("Init", Init),
        DECLARE_NAPI_FUNCTION("DeInit", DeInit),
        DECLARE_NAPI_FUNCTION("Process", Process),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(napi_property_descriptor), desc));
    return exports;
}

static napi_module _module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "ai_service_js",
    .nm_priv = ((void *)0),
    .reserved = {0}};

extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&_module);
}
} // namespace AIS
} // namespace OHOS